import { secondsToHourMinuteSecond } from "~/utils/util";
import {
  Cube
} from "./Cube"
import {
  GameConfig
} from "./GameConfig";
import {
  ObjectPool
} from "./ObjectPool";
import { Wall } from "./Wall";

class GameManager {
  currentTime = 0;
  currentBeat = 0;
  aliveCubes = {};
  aliveWalls = {};

  allCubes = [];
  allWalls = [];
  cubePool = null;
  wallPool = null;

  init(THREE, scene) {
    this.THREE = THREE
    this.scene = scene;

    this.cubePool = new ObjectPool(() => {
      let cube = new Cube(this.scene);
      cube.init(this.THREE, 0xff0000);
      cube.setAlive(false)

      this.allCubes.push(cube)
      return cube
    })
    this.cubePool.init()

    this.wallPool = new ObjectPool(()=>{
      let wall = new Wall(this.scene);
      wall.init(this.THREE)
      wall.setAlive(false)
      this.allWalls.push(wall)
      return wall;
    })

    this.wallPool.init()
  }

  getObjectBirthBeat(objBeat) {
    const reachPlayerTime = this.mapParser.TimeFromBeat(objBeat)
    const birthTime = reachPlayerTime - GameConfig.GetNoteSpeed() * GameConfig.BirthDistance;
    return this.mapParser.BeatFromTime(birthTime)
  }

  pushIntoAlive(Beat, cube, aliveObjects){
    if(!(Beat in aliveObjects)){
      aliveObjects[Beat] = [];
    }
    aliveObjects[Beat].push(cube)
  }
  
  alreadyInAlivePool(note, aliveObjects){
    const Beat = note.Beat;
    if(!(Beat in aliveObjects)){
      return false;
    }
    return undefined != aliveObjects[Beat].find(ele=>ele?.getEvent()?.Index == note.Index)
  }

  deactiveDeadObjects(startBeat, endBeat, aliveObjects, pool){
    const Beats = Object.keys(aliveObjects)
    for (const Beat of Beats) {
      if (Beat < startBeat || Beat > endBeat) {
        // console.log("Removing beat:" + Beat)
        const targetObjects = aliveObjects[Beat]
        for(const targetObject of targetObjects){
          targetObject.setAlive(false)
          delete aliveObjects[Beat] // Remove this beat from aliveCubes
          pool.release(targetObject)
        }
      }
    }
  }

  updateCurrentTime(time) {
    if (time >= this.parser.getSongLength() * 1000.0) {
      console.log("Song stopped")
      this.currentTime = this.parser.getSongLength()
      this.currentBeat = this.mapParser.BeatFromTime(this.currentTime)
      return;
    }

    this.currentTime = time / 1000.0;
    this.currentBeat = this.mapParser.BeatFromTime(this.currentTime);

    const startBeat = Math.max(0, this.mapParser.BeatFromTime(this.currentTime - GameConfig.DieDistance / GameConfig.GetNoteSpeed())) // Can't be smaller than 0
    const endBeat = this.mapParser.BeatFromTime(this.currentTime + GameConfig.BirthDistance / GameConfig.GetNoteSpeed())

    // console.log("Start Beat:", startBeat)
    // console.log("End Beat:", endBeat)

    // Deactive all dead cubes
    this.deactiveDeadObjects(startBeat, endBeat, this.aliveCubes, this.cubePool);
    this.deactiveDeadObjects(startBeat, endBeat, this.aliveWalls, this.wallPool);

    const _this = this
    this.mapParser.runOnRangeNotes(startBeat, endBeat, note => {
      if (!this.alreadyInAlivePool(note, this.aliveCubes)) {
        if(note.Type === 0 || note.Type === 1){
          let newCube = _this.cubePool.acquire()
          newCube.setEvent(note)
          newCube.setAlive(true)
          // console.log("Adding beat:" + note.Beat)
          _this.pushIntoAlive(note.Beat, newCube, this.aliveCubes)
        }else{
          // console.log("Bomb!")
        }
      }
    })

    this.mapParser.runOnRangeWalls(startBeat, endBeat, wallEvent=>{
      if(!this.alreadyInAlivePool(wallEvent, this.aliveWalls)){
        let newWall = _this.wallPool.acquire()
        wallEvent.Length = _this.mapParser.TimeFromBeat(wallEvent.Duration) * GameConfig.GetNoteSpeed()
        newWall.setEvent(wallEvent)
        newWall.setAlive(true)
        // console.log("Adding beat:" + note.Beat)
        _this.pushIntoAlive(wallEvent.Beat, newWall, this.aliveWalls)
      }
    });

    // Update the position of all Cubes
    function updatePosition(objArray){
      Object.values(objArray).forEach(objs=>Object.values(objs).forEach( (obj) => {
        const reachPlayerTime = _this.mapParser.TimeFromBeat(obj.getEvent().Beat) // The time the cube should reach the player.
        const remainingTime = reachPlayerTime - _this.currentTime
        const remainingDistance = GameConfig.GetNoteSpeed() * remainingTime
        obj.getMesh().position.z = -remainingDistance
      }))
    }

    updatePosition(this.aliveCubes);
    updatePosition(this.aliveWalls);
  }

  constructor(parser, diff) {
    this.parser = parser;
    this.mapParser = this.parser.loadDiffFile(diff);
  }
}

export {
  GameManager
}